/**
 * @file RinexObsID.cpp
 * gpstk::RinexObsID - Identifies types of observations
 */

//============================================================================
//
//  This file is part of GPSTk, the GPS Toolkit.
//
//  The GPSTk is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published
//  by the Free Software Foundation; either version 2.1 of the License, or
//  any later version.
//
//  The GPSTk is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with GPSTk; if not, write to the Free Software Foundation,
//  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
//
//  Copyright 2004, The University of Texas at Austin
//
//============================================================================


#include "RinexObsID.hpp"
#include "SatID.hpp"
#include "StringUtils.hpp"

using namespace std;
namespace gpstk
{
    /// Construct this object from the string specifier
    RinexObsID::RinexObsID(const std::string& strID)
        noexcept(false)
    {
        if(!isValidRinexObsID(strID))
        {
            InvalidParameter ip(strID + " is not a valid RinexObsID");
            GPSTK_THROW(ip);
        }
        try
        {
            ObsID obsid(strID);
            *this = RinexObsID(obsid.type, obsid.band, obsid.code);
            //*this = RinexObsID(obsid);
        }
        catch(InvalidParameter& ip)
        { GPSTK_RETHROW(ip); }
    }


    // Represent this object using the Rinex3 notation
    std::string RinexObsID::asString() const
    {
        char buff[4];

        buff[0] = ot2char[type];
        buff[1] = cb2char[band];
        buff[2] = tc2char[code];
        buff[3] = 0;
        return std::string(buff);
    }


    namespace StringUtils
    {
        // convert this object to a string representation
        std::string asString(const RinexObsID& p)
        {
            return p.asString();
        }
    }


    // RINEX 3.03 document, section 5.1
    // GPS
    //       L1   C,S,L,X,P,W,Y,M,N(but not C1N)       G 1 CSLXPWYMN (but not C1N)
    //       L2   C,D,S,L,X,P,W,Y,M,N(but not C2N)     G 2 CDSLXPWYMN (but not C1N)
    //       L5   I,Q,X                                G 5 IQX
    // GLO
    //       G1   C,P                                  R 1 CP
    //       G2   C,P                                  R 2 CP
    //       G3   I,Q,X                                R 3 IQX
    // GAL
    //       E1   A,B,C,X,Z                            E 1 ABCXZ
    //       E5a  I,Q,X                                E 5 IQX
    //       E5b  I,Q,X                                E 7 IQX
    //       E5ab I,Q,X                                E 8 IQX
    //       E6   A,B,C,X,Z                            E 6 ABCXZ
    // SBAS
    //       L1   C                                    S 1 C
    //       L5   I,Q,X                                S 5 IQX
    // QZSS
    //       L1   C,S,L,X,Z                            J 1 CSLXZ
    //       L2   S,L,X                                J 2 SLX
    //       L5   I,Q,X                                J 5 IQX
    //       L6   S,L,X                                J 6 SLX
    // BDS
    //       B1   I,Q,X                                C 2 IQX
    //       B2   I,Q,X                                C 7 IQX
    //       B3   I,Q,X                                C 6 IQX
    // IRNSS
    //       L5   A,B,C,X                              I 1 ABCX
    //       S    A,B,C,X                              I 2 ABCX

    // Determine if the given ObsID is valid. If the input string is 3
    // characters long, the system is assumed to be GPS. If this string is 4
    // characters long, the first character is the system designator as
    // described in the Rinex 3 specification.
    bool isValidRinexObsID(const std::string& strID)
    {
        int i(strID.length()-3);
        if(i < 0 || i > 1) return false;

        char sys;
        std::string id;

        if(i == 1)
        {
            sys = strID[0];
            id = strID.substr(1);
            return isValidRinexObsID(id,sys);
        }

        // test all RINEX systems
        std::string syss(ObsID::validRinexSystems);
        for(size_t j=0; j<syss.size(); j++)
            if(isValidRinexObsID(strID,syss[j])) return true;

        return false;
    }


    // Determine if the given ObsID is valid, for the given system
    bool isValidRinexObsID(const std::string& strID, const char sys)
    {
        if(strID.length() != 3) return false;
        char ot(strID[0]);
        char cb(strID[1]);
        char tc(strID[2]);
        std::string codes(ObsID::validRinexTrackingCodes[sys][cb]);
        if(codes.find(std::string(1,tc)) == std::string::npos)
            return false;
        if(sys == 'G' && ot == 'C' && tc == 'N')           // the one exception
            return false;

        return true;
    }


    std::ostream& RinexObsID::dumpCheck(std::ostream& s)
        noexcept(false)
    {
        try
        {
            const std::string types("CLDS");
            std::map<char,std::string>::const_iterator it;

            for(size_t i=0; i<ObsID::validRinexSystems.size(); i++)
            {
                char csys = ObsID::validRinexSystems[i];
                std::string sys = ObsID::validRinexSystems.substr(i,1);
                SatID sat(sys);
                std::string system(sat.systemString());

                s << "System " << sys << " = " << system << ", frequencies ";
                for(it = ObsID::validRinexTrackingCodes[sys[0]].begin();
                    it != ObsID::validRinexTrackingCodes[sys[0]].end();
                    ++it)
                    s << it->first;
                s << std::endl;

                for(it = ObsID::validRinexTrackingCodes[sys[0]].begin();
                    it != ObsID::validRinexTrackingCodes[sys[0]].end();
                    ++it)
                {
                    s << "   " << system << "(" << sys << "), freq " << it->first
                      << ", codes '" << it->second << "'" << std::endl;
                    std::string codes(it->second), str;
                    for(size_t j=0; j<codes.size(); ++j)
                    {
                        std::ostringstream oss1;
                        for(size_t k=0; k<types.size(); ++k)
                        {
                            str = std::string(1,types[k])
                                + std::string(1,it->first)
                                + std::string(1,codes[j]);
                            std::ostringstream oss;
                            if(!isValidRinexObsID(str,csys))
                                oss << str << " " << "-INVALID-";
                            else
                            {
                                RinexObsID robsid(sys+str);
                                oss << str << " " << robsid;
                            }
                            oss1 << " " << StringUtils::leftJustify(oss.str(),34);
                        }
                        s << StringUtils::stripTrailing(oss1.str()) << std::endl;
                    }
                }
            }
        }
        catch(Exception& e)
        {
            s << "Exception: " << e.what() << std::endl;
            GPSTK_RETHROW(e);
        }

        return s;
    }

}  // end namespace
